package com.xuzhiguang.lightnat.client.proxy;

import com.xuzhiguang.lightnat.client.Client;
import com.xuzhiguang.lightnat.client.proxy.handler.*;
import com.xuzhiguang.lightnat.common.manager.ProxyChannelManager;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ProxyClient implements Client {

    private EventLoopGroup group;

    private Bootstrap bootstrap;

    @Override
    public void start() {
        group = new NioEventLoopGroup(new DefaultThreadFactory("p-client"));

        bootstrap = new Bootstrap();
        LoggingHandler loggingHandler = new LoggingHandler(LogLevel.INFO);
        InactiveHandler inactiveHandler = new InactiveHandler();
        ActiveHandler activeHandler = new ActiveHandler();
        TransferHandler transferHandler = new TransferHandler();
        WritabilityChangedHandler writabilityChangedHandler = new WritabilityChangedHandler();
        ExceptionHandler exceptionHandler = new ExceptionHandler();

        bootstrap.group(group)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .handler(loggingHandler)
                .handler(new ChannelInitializer<SocketChannel>() {

                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline()
                                .addLast("idleCheckHandler", new IdleCheckHandler())
                                .addLast("byteArrayEncoder", new ByteArrayEncoder())
                                .addLast("transferHandler", transferHandler)
                                .addLast("activeHandler", activeHandler)
                                .addLast("inactiveHandler", inactiveHandler)
                                .addLast("writabilityChangedHandler", writabilityChangedHandler)
                                .addLast("exceptionHandler", exceptionHandler);
                    }
                });
    }


    @Override
    public void stop() {
        ProxyChannelManager.getAll().forEach((aLong, channel) -> channel.close());
        this.group.shutdownGracefully();
    }

    public boolean connect(long sessionId, String sourceHost, Integer sourcePort) {
        ChannelFuture f = null;
        try {
            f = bootstrap.connect(sourceHost, sourcePort).sync();
        } catch (InterruptedException e) {
            log.info("proxy connect fail. sessionId:{}, host:{}, port:{}", sessionId, sourceHost, sourcePort, e);
            return false;
        }

        if (f.isSuccess()) {
            ProxyChannelManager.add(sessionId, f.channel());
            log.info("proxy connect success. channel:{}, sessionId:{}, host:{}, port:{}", f.channel(), sessionId, sourceHost, sourcePort);
        }
        return f.isSuccess();
    }

}
